import json
import logging
from datetime import datetime

import numpy as np
import pandas as pd
from scipy.stats import norm
from api import utils

from api import models
from api.pojie_A10.producer_actual_values_yqd_A10 import SampleProducer_actual_values_yqd_A10
from api.pojie_A10.producer_actual_values_A10 import SampleProducer_actual_values_A10
from api.serial.analyse import cal_madis_whole_curve, cal_madis, cal_seg_times, noise_detection, smoothing_process_v2, \
    smoothing_process_v1

import math

from detecter.settings import Collection_frequency

logger = logging.getLogger('serial.sample')


def transfer(frame_arr):
    frame_arr[0] = datetime.strptime(frame_arr[0], "%Y-%m-%d %H:%M:%S.%f")
    for i in range(1, len(frame_arr)):
        frame_arr[i] = int(frame_arr[i])
    return frame_arr


# 增加从文件中初始化的方式, 用is_load控制
# zxy 更改于2019.10.31
class ProductSample:
    def __init__(self, sample_id, frame_list, is_load=False, json_obj=None):
        self.id = sample_id
        self.__tem_list = []
        # 实际压力
        self.actual_pressure = []
        # 实际位置
        self.actual_location = []
        # 实际速度
        self.actual_speed = []
        # 设定压力
        self.setting_pressure = []
        # 设定速度
        self.setting_speed = []
        self.seg_points = []  # 分段点
        # 以下属性需根据正样本画像计算后得到
        # 整个样本马氏距离
        self.total_madis = None
        # 分段马氏距离(6段)
        self.seg_madis_list = []
        # 分段时长差
        self.seg_time_diff_list = []
        # 总体不良置信度
        self.total_rate = None
        # 分段不良置信度
        self.seg_rate_list = []
        self.time_info = []
        # 权重
        self.alpha = 0.5

        # zxy 修改于 20200508 增加样本帧数量
        self.frame_length = len(frame_list)

        if is_load:
            self.load_from_file(json_obj)
        else:
            # 处理帧数组得到对应的数据  ， frame_list 是一个二维列表
            self.generate_userful_info(frame_list)

    # 从文件中读取sample
    # by zxy on 2019.10.31
    def load_from_file(self, json_obj):
        self.actual_pressure = json_obj['actual_pressure']
        self.actual_location = json_obj['actual_location']
        self.actual_speed = json_obj['actual_speed']

    # 从帧列表中获取有效信息
    def generate_userful_info(self, frame_list):
        logger.info("len of frame_list: " + str(len(frame_list)))
        df = pd.DataFrame(frame_list)
        # logger.info("flag pandas ")
        # 提取注射段和保压段的数据，不过这里面好像没有保压段的数据，原因在synchronous_setting_of_real_time_values这个方法里面
        # focus_period = self.get_focus_period(df)
        focus_period = df
        # 获取时间相关的信息
        self.time_info = self.get_time_info(frame_list, focus_period)
        # 获取各个字段的数据
        self.actual_pressure, self.actual_location, self.actual_speed, self.setting_pressure, self.setting_speed = self.get_attribute_list(
            focus_period, df)  # 注意这里返回的每个值都是一个列表，列表里面存储的是相同类型的值

        # 给注射段和保压段 分段 这里获取射胶段和保压段数据时，获取的是对的，没有问题。
        self.seg_points = self.curve_segmentation(self.actual_pressure, self.setting_pressure)

    def save_in_file(self, out_file):
        utils.check_dir_exist(out_file)
        dic = self.get_userful_info_dic()  # 得到一个字典，存储着相关信息
        line = json.dumps(dic, cls=NpEncoder)  # 转变成json字符串
        with open(out_file, mode='a+') as writer:  # 写入文件
            writer.write(line + '\n')

    def get_period_len(self):
        if self.time_info:
            return self.time_info[5]
        else:
            return None

    def get_start_time(self):
        if self.time_info:
            return self.time_info[0]
        else:
            return None

    def get_userful_info_dic(self):
        '''
        这里面的数据都是创建样本对象时，调用self.generate_userful_info(frame_list)这个方法得到的
        '''
        return {
            'id': self.id,
            'time_info': self.time_info,
            'seg_points': self.seg_points,
            'actual_pressure': self.actual_pressure,
            'actual_location': self.actual_location,
            'actual_speed': self.actual_speed,
            'setting_pressure': self.setting_pressure,
            'setting_speed': self.setting_speed
        }

    def keep_sample_record(self, mould, state=0, label=None, reason=None, times=0):
        end_time = self.time_info[1]  # 以结束时间计算
        try:
            sample = models.Sample.objects.create(mould=mould, prod_time=end_time, sample_id=self.id, state=state,
                                                  times=times)
            if label:
                sample.label = label
            if reason:
                sample.reason = reason
            sample.save()
        except Exception as e:
            logger.error(f'编号为{self.id}样本存入数据库失败,{e}')

    def get_tem_list(self):
        return self.__tem_list

    # 以下所有方法需要正样本画像才可调用
    # 先算总体马氏距离,才能算总体的置信度
    # 先算分段马氏距离与时间差,才能算分段的置信度
    def set_tem_list(self, temp_list):
        self.__tem_list = temp_list

    def cal_total_madis(self, count, portrait_feat, feat_var):
        self.total_madis = cal_madis_whole_curve(count, self.actual_pressure, self.actual_location, self.actual_speed,
                                                 portrait_feat, feat_var)
        return self.total_madis

    # 防止方差为0 zxy 修改于 20200114
    def cal_total_rate(self, feat_dis, dis_var):
        self.total_rate = np.abs(
            1 - (1 - norm.cdf((self.total_madis - feat_dis) / (np.sqrt(dis_var + 1e-10) * 2))))
        return self.total_rate

    # 防止方差为0 zxy 修改于 20200114
    def cal_total_madis_times(self, feat_dis, dis_var):
        times = (self.total_madis - feat_dis) / np.sqrt(dis_var + 1e-10)
        self.total_rate = np.abs(times)

    # 获取样本分段的马氏距离
    def cal_segs_madis(self, len_seg_list, portrait_feat_seg_list, feat_var_seg_list):
        self.seg_madis_list = []
        for i in range(len(self.seg_points) - 1):
            sample = np.linspace(self.seg_points[i], self.seg_points[i + 1], len_seg_list[i], endpoint=False,
                                 dtype=np.int32)
            feature = np.concatenate(
                [np.array(self.actual_pressure)[sample], np.array(self.actual_location)[sample],
                 np.array(self.actual_speed)[sample]])
            # 本段的特征,正样本画像该段的特征与方差
            seg_feat = np.array(portrait_feat_seg_list[i])
            seg_var = np.array(feat_var_seg_list[i])
            dist_seg = cal_madis(feature, seg_feat, seg_var)
            self.seg_madis_list.append(dist_seg)
        return self.seg_madis_list

    # 获取样本总体的时间差list
    def cal_segs_time_diff(self, portrait_seg_points):
        self.seg_time_diff_list = []
        for i in range(len(self.seg_points) - 1):
            td = (self.seg_points[i + 1] - self.seg_points[i] - portrait_seg_points[i + 1] + portrait_seg_points[
                i]) / 80
            self.seg_time_diff_list.append(td)
        return self.seg_time_diff_list

    # 分段异常率使用 (分段马氏距离 - 样本均值)/方差的方式
    # by zxy on 20191227
    def get_segs_rate(self, madis_mean_list, mean_var_list, td_mean_list, td_var_list):
        self.seg_rate_list = []
        seg_madis_list = self.seg_madis_list
        seg_time_diff_list = self.seg_time_diff_list
        alpha = self.alpha
        for i in range(len(seg_madis_list)):
            prob_seg_dist = cal_seg_times(seg_madis_list[i], madis_mean_list[i], mean_var_list[i])
            prob_seg_td = cal_seg_times(seg_time_diff_list[i], td_mean_list[i], td_var_list[i])
            prob_seg_total = alpha * prob_seg_dist + (1 - alpha) * prob_seg_td
            self.seg_rate_list.append([prob_seg_dist, prob_seg_td, prob_seg_total])
        return self.seg_rate_list

    # 保留指定位数
    def round_for_ui(self):
        if self.total_rate and self.seg_rate_list:
            self.total_rate = round(float(self.total_rate), 3)
            # 若太接近，会出现NaN，这个时候用0.0代替
            # zxy 修改于20191115
            for seg_rate in self.seg_rate_list:
                seg_rate[0] = round(float(seg_rate[0]), 2)
                if np.isnan(seg_rate[0]):
                    seg_rate[0] = 0.0
                seg_rate[1] = round(float(seg_rate[1]), 2)
                if np.isnan(seg_rate[1]):
                    seg_rate[1] = 0.0
                seg_rate[2] = round(float(seg_rate[2]), 2)
                if np.isnan(seg_rate[2]):
                    seg_rate[2] = 0.0
        else:
            logger.warning(f'样本{self.id}的置信度不存在')

    @staticmethod
    def get_time_info(frame_list, focus_period):
        list_len = len(frame_list)
        start_time = frame_list[0][0]
        end_time = frame_list[-1][0]
        period_begin = focus_period.iloc[0, 0]
        period_end = focus_period.iloc[-1, 0]
        period_len = len(focus_period)
        cycle = round((frame_list[-1][1] - frame_list[0][1]) / 1000, 3)  # 一个产品的周期  注意，由于一条数据里面的日期，时间戳都是在程序里面赋值的，考虑到网络中各种因素影响，赋值的时间是不准确的，不应该用来计算周期。
        return [start_time, end_time, list_len, period_begin, period_end, period_len, cycle]

    @staticmethod
    def get_focus_period(id1):
        # return id1[id1[7] == 1]  # 根据注射信号获取注射段和保压段
        return id1[id1[7] != 0]  # 这里如果不等于0就是只取了射胶段的。所以这里的代码应该是有问题的。

    @staticmethod
    def get_attribute_list(two_stage, df):
        # actual_pressure = two_stage.iloc[:, 5].tolist()
        # begin_index = two_stage.index[0]
        # end_index = two_stage.index[-1]
        # # 注射和保压两段的螺杆位置,往前延申十个
        # actual_location = df.iloc[begin_index - 10:end_index, 6]
        # actual_location = actual_location.tolist()
        # actual_location = noise_detection(actual_location)
        # actual_location = smoothing_process_v1(actual_location)
        # # 设定压力
        # setting_pressure = two_stage.iloc[:, 3].tolist()
        # # 设定速度
        # setting_speed = two_stage.iloc[:, 4].tolist()
        # # 注射和保压两段的实际速度
        # actual_speed = [0] * (len(actual_location) - 10)
        # for j in range(10, len(actual_speed)):
        #     # zxy 修改于 20200422 实际速度返回绝对值
        #     actual_speed[j] = abs((actual_location[j] - actual_location[j - 10])) * Collection_frequency
        # actual_location = actual_location[10:]

        actual_pressure = two_stage.iloc[:, 5].tolist()
        actual_location = two_stage.iloc[:, 6].tolist()  # 实际螺杆位置
        actual_speed = two_stage.iloc[:, 11].tolist()
        setting_pressure = two_stage.iloc[:, 3].tolist()
        setting_speed = two_stage.iloc[:, 4].tolist()
        return actual_pressure, actual_location, actual_speed, setting_pressure, setting_speed

    # by qkw on 20240124
    @staticmethod
    def curve_segmentation(id1_ch1, id2_ch1, isSample=True):  # portrait_actual_pressure, portrait_setting_pressure
        sampleproducer_actual_values = SampleProducer_actual_values_A10.get_instance()
        global_shejiaobaoyafendaun = sampleproducer_actual_values.global_shejiaobaoyafendaun
        # print(id2_ch1)
        # global_shejiaobaoyafendaun = min(id2_ch1, key=lambda x: x[2])
        return global_shejiaobaoyafendaun

        # segPoint = [0, ]
        # for i in range(len(id2_ch1) - 1):
        #     if id2_ch1[i] != id2_ch1[i + 1]:
        #         segPoint.append(i)
        #
        # return segPoint

    # # by zxy on 20191227
    # @staticmethod
    # def curve_segmentation(id1_ch1, id2_ch1, isSample=True):  # portrait_actual_pressure, portrait_setting_pressure
    #
    #     threshold = Collection_frequency * 10  # 80
    #     seg_point1 = 0  # COM2的射胶信号变为1的点为射胶开始
    #     is_smooth = 5  # 判断是否是设定压力平滑区间的阈值
    #     is_smooth_flag = False
    #
    #     # 工艺人员接受注塑培训后再启用
    #     # seg_point1 = np.argsort(id1_ch1)[-1]  # COM2的射胶信号变为1的点为射胶开始
    #     if not isSample:
    #         threshold = Collection_frequency * 5  # 40
    #     # logger.info("seg_point1(start): " + str(seg_point1))
    #
    #     # 保压切换点
    #     pressure_change_point = []
    #     i = seg_point1 + 10
    #     # 去掉最后的20帧
    #     while i < len(id2_ch1) - 20:
    #         # 判断是否为尖峰点，依据为10个点內有同时有升高和降低
    #         if isSample and id2_ch1[i + 3] - id2_ch1[i + 10] > threshold and id2_ch1[i + 3] - id2_ch1[i] > threshold:
    #             #             logger.info(f"尖峰点{i + 2}")
    #             #             logger.info(id2_ch1[i:i + 10])
    #             i = i + 5
    #             continue
    #         #         if np.abs(id2_ch1[i + 1] - id2_ch1[i]) + np.abs(id2_ch1[i + 2] - id2_ch1[i + 1]) > threshold:
    #         #             pressure_change_point.append(i)
    #         #             i = i + 10
    #
    #         # by qkw on 20230214
    #         # 连续5帧数据变化小于10，则认为段点之前是平滑的
    #         if np.abs(id2_ch1[i + 1] - id2_ch1[i]) < is_smooth:
    #             if np.abs(id2_ch1[i + 2] - id2_ch1[i + 1]) < is_smooth:
    #                 if np.abs(id2_ch1[i + 3] - id2_ch1[i + 2]) < is_smooth:
    #                     # if np.abs(id2_ch1[i + 4] - id2_ch1[i + 3]) < is_smooth:
    #                     # if np.abs(id2_ch1[i + 5] - id2_ch1[i + 4]) < is_smooth:
    #                     is_smooth_flag = True
    #
    #         if np.abs(id2_ch1[i + 1] - id2_ch1[i]) + np.abs(id2_ch1[i + 2] - id2_ch1[i + 1]) + np.abs(id2_ch1[i + 3] - id2_ch1[i + 2]) > threshold and is_smooth_flag:
    #             pressure_change_point.append(i)
    #             is_smooth_flag = False
    #             i = i + 3
    #         else:
    #             i = i + 1
    #
    #     segPoint = [0] + pressure_change_point
    #     # # 还是保留最后一个分段点
    #     # segPoint.append(len(id1_ch1) - 1)
    #     #     logger.info(segPoint)
    #
    #     return segPoint


class NpEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, np.integer):
            return int(obj)
        elif isinstance(obj, np.floating):
            return float(obj)
        elif isinstance(obj, np.ndarray):
            return obj.tolist()
        elif isinstance(obj, datetime):
            return obj.strftime('%Y-%m-%d %H:%M:%S.%f')
        else:
            return super(NpEncoder, self).default(obj)


if __name__ == '__main__':
    pass
